
#include "Entity.h"
#include "Engine.h"
#include "TEString.h"
#include "SubdivisionGeom.h"
#include "BillboardGeom.h"
#include "TerrainGeom.h"
#include "Octree.h"

#define forchildren for(unsigned int i = 0; i < children.size(); ++i)

//#define DRAW_PLAYER_MODEL

//
// Constructor
//

Entity::Entity()
{
	parent = 0;
	geometry = 0;
	updateTransform = true; 
	updateRotation = true;
	worldMatrix = new D3DXMATRIX(); 
	rotationMatrix = new D3DXMATRIX();

	gravMod = 1.0;
	octree = 0;
	//rotBox = 0;
}

Entity::Entity(Entity *_parent, const string &_name)
{
	parent = _parent;
	name = _name;
	geometry = 0;
	updateTransform = true; 
	updateRotation = true;
	worldMatrix = new D3DXMATRIX(); 
	rotationMatrix = new D3DXMATRIX();

	gravMod = 1.0;
	octree = 0;
	//rotBox = 0;

	if(parent)
		parent->AddChild(this);
}

//
// Clears the Children
//

void Entity::ClearChildren()
{
	forchildren
	{
		children[i]->ClearChildren();
		delete(children[i]);
	}

	children.clear();
}

//
// Render
//

void Entity::Render()
{
	#ifdef USE_MATERIALS
	LoadMaterial();
	#endif
	engine.LoadMatrices(WorldMatrix());

	// Try to render current entity
	#ifdef DRAW_PLAYER_MODEL
	if(geometry)
	#else
	if(geometry && this != engine.player.Model())
	#endif
	{
		geometry->Render();
	}
	
	// Try to render children
	forchildren
	{
		children[i]->Render();
	}
}

//
// SetPosition/Rotation
//


void Entity::SetPosition(Vector3 _position) 
{ 
	position = _position; 
	updateTransform = true; 
}

void Entity::SetRotation(Euler3 _rotation) 
{ 
	rotation = _rotation; 
	updateTransform = true; 
	updateRotation = true; 
}

//
// Translate
//

void Entity::Translate(Real3 vec)
{
	position += vec;// * engine.lastFrameLength; 
	updateTransform = true;
}

//
// Rotate
//

void Entity::Rotate(Real3 rot)
{
	rotation += rot;// * engine.lastFrameLength; 
	updateTransform = true;
	updateRotation = true;
}

//
// GenerateBox
//

void Entity::GenerateBox()
{
	baseBox.Clear();
	if(geometry) geometry->CalculateBBox();
}

void Entity::GenerateOctree()
{
	if(geometry && !geometry->IsTerrainGeom()) 
	{
		if(octree) delete octree;
		octree = new Octree(baseBox.Min(), baseBox.Extent());
		geometry->CalculateBBoxOctree();
	}
}

//
// AddBBoxPoint
//

void Entity::AddBBoxPoint(const Real3 &p)
{
	baseBox.AddPoint(p);
}

//
// AddOctreePoint
//

void Entity::AddOctreeTri(const Real3 a, const Real3 b, const Real3 c)
{
	// Add rotation
	D3DXVec3TransformCoord((D3DXVECTOR3*)&a, (D3DXVECTOR3*)&a, rotationMatrix);
	D3DXVec3TransformCoord((D3DXVECTOR3*)&b, (D3DXVECTOR3*)&b, rotationMatrix);
	D3DXVec3TransformCoord((D3DXVECTOR3*)&c, (D3DXVECTOR3*)&c, rotationMatrix);
	octree->AddTri(a, b, c);
}

//
// UpdateTransform
//

void Entity::UpdateTransform()
{
	if(gravMod * engine.gravity && (geometry ? !geometry->IsTerrainGeom() : true))
	{
		updateTransform = true;
		linearVel.y += gravMod * engine.gravity * engine.lastFrameLength;
	}

	float engineHeight = -INFINITY;
	if(engine.terrain) engineHeight = engine.terrain->GetHeight(position);

	if(!linearVel.IsZero())
	{
		updateTransform = true;
		if(geometry ? !geometry->IsTerrainGeom() : true)
		{
			Real3 oldPos = position;

			position += linearVel * engine.lastFrameLength;
			if(octree && engine.CollisionTest(this))
			{
				position = oldPos;
				linearVel.Set(0.0f, 0.0f, 0.0f);
			}/**/

			if(position.y < engineHeight)
			{
				position.y = engineHeight;
				linearVel.y = 0;
			}
		}
	}

	if(!rotationalVel.IsZero())
	{
		updateTransform = true;
		updateRotation = true;
		rotation += rotationalVel * engine.lastFrameLength;
	}
	
	// Update children
	forchildren
		children[i]->UpdateTransform();
}

//
// Returns Geometry
//

Geometry *Entity::Geom() const
{
	return geometry;
}

SubdivisionGeom *Entity::SubGeom() const
{
	if(geometry && geometry->IsSubdivisionGeom())
		return (SubdivisionGeom*)geometry;
	return 0;
}

BillboardGeom *Entity::BBGeom() const
{
	if(geometry && geometry->IsBillboardGeom())
		return (BillboardGeom*)geometry;
	return 0;
}

TerrainGeom *Entity::Terrain() const
{
	if(geometry && geometry->IsTerrainGeom())
		return (TerrainGeom*)geometry;
	return 0;
}

//
// Create Geometry
//

SubdivisionGeom *Entity::CreateSubGeom()
{
	if(!geometry)
		return (SubdivisionGeom*)(geometry = new SubdivisionGeom(this));
	else if(geometry->IsSubdivisionGeom())
		return (SubdivisionGeom*)geometry;
	return 0;
}

BillboardGeom *Entity::CreateBBGeom(bool isFixedPoint)
{
	if(!geometry)
		return (BillboardGeom*)(geometry = new BillboardGeom(this, isFixedPoint));
	else if(geometry->IsBillboardGeom())
		return (BillboardGeom*)geometry;
	return 0;
}

TerrainGeom *Entity::CreateTerrainGeom(vector<vector<uchar>> &hMap, ushort numDivisions, Level detail)
{
	if(!geometry)
		return (TerrainGeom*)(geometry = new TerrainGeom(this, hMap, numDivisions, detail));
	else if(geometry->IsSubdivisionGeom())
		return (TerrainGeom*)geometry;
	return 0;
}

//
// SetGeom
//

Geometry *Entity::SetGeom(Geometry *geom)
{
	if(geometry) return geometry;
	return geometry = geom;
}

//
// AddChild
//

void Entity::AddChild(Entity *child)
{
	children.push_back(child);
}

//
// WorldMatrix
//

D3DXMATRIX *Entity::WorldMatrix() 
{
	if(updateTransform)
	{
    // Using the left-to-right order of matrix concatenation,
    // apply the translation to the object's world position
    // before applying the rotations.
    D3DXMatrixTranslation(worldMatrix, position.x, position.y, position.z);

		if(updateRotation)
		{
			D3DXMatrixIdentity(rotationMatrix);

			// Now, apply the orientation variables to the world matrix
			// Temporary matrices
			D3DXMATRIX temp;

			// Produce and combine the rotation matrices.
			D3DXMatrixRotationX(&temp, rotation.x);           // Pitch
			D3DXMatrixMultiply(rotationMatrix, rotationMatrix, &temp);

			D3DXMatrixRotationY(&temp, rotation.y);           // Yaw
			D3DXMatrixMultiply(rotationMatrix, rotationMatrix, &temp);

			D3DXMatrixRotationZ(&temp, rotation.z);           // Roll
			D3DXMatrixMultiply(rotationMatrix, rotationMatrix, &temp);

			// Apply the rotation matrices to complete the world matrix.
			D3DXMatrixMultiply(worldMatrix, rotationMatrix, worldMatrix);

			// Generate Octree
			/*if(baseBox.IsGood())
			{
				if(rotBox) delete rotBox;
				rotBox = new BBox(baseBox, rotationMatrix);
				//MessageBox(0, L"Generating Octree.", ConvertToTChar(name), 0);
				//TimeMethod(GenerateOctree());
				GenerateOctree();
			}*/

			updateRotation = false;
		}

		updateTransform = false;
	}

	return worldMatrix;
}

//
// SetMaterial
//

void Entity::SetMaterial(Color diffuse, Color ambient, Color specular, 
												 Color emissive, float power) 
{
	material.Diffuse.a = diffuse.AlphaF();
	material.Diffuse.r = diffuse.RedF();
	material.Diffuse.g = diffuse.GreenF();
	material.Diffuse.b = diffuse.BlueF();

	material.Ambient.a = ambient.AlphaF();
	material.Ambient.r = ambient.RedF();
	material.Ambient.g = ambient.GreenF();
	material.Ambient.b = ambient.BlueF();

	material.Specular.a = specular.AlphaF();
	material.Specular.r = specular.RedF();
	material.Specular.g = specular.GreenF();
	material.Specular.b = specular.BlueF();

	material.Emissive.a = emissive.AlphaF();
	material.Emissive.r = emissive.RedF();
	material.Emissive.g = emissive.GreenF();
	material.Emissive.b = emissive.BlueF();

	material.Power = power;
}

//
// LoadMaterial
//

void Entity::LoadMaterial() 
{
	engine.direct3DRenderer->SetMaterial(&material);
}

//
// TreeBox
//

BBox Entity::TreeBox()
{
	BBox tBox = baseBox;

	forchildren
		tBox += children[i]->TreeBox();

	return tBox;
}

//
// GetBBoxVertsSize
//

uint Entity::GetBBoxVertsSize()
{
	uint s = 0;
	if(baseBox.IsGood())
		s = 24 * sizeof(Real3);

	forchildren
		s += children[i]->GetBBoxVertsSize();

	return s;
}

//
// GetBBoxVerts
//

void Entity::GetBBoxVerts(vector<Real3> &ptr)
{
	if(octree)
		octree->GetBoxPoints(ptr, position);

	forchildren
		children[i]->GetBBoxVerts(ptr);
}

//
// Subdivision
//

void Entity::IncreaseSubdivision()
{
	if(geometry && geometry->IsSubdivisionGeom())
		((SubdivisionGeom*)geometry)->IncreaseSubLevel();

	forchildren
		children[i]->IncreaseSubdivision();
}

void Entity::DecreaseSubdivision()
{
	if(geometry && geometry->IsSubdivisionGeom())
		((SubdivisionGeom*)geometry)->DecreaseSubLevel();

	forchildren
		children[i]->DecreaseSubdivision();
}

//
// Writes everything to file
//

void Entity::Write(FileStream &file)
{
	if(file.IsBinary())
	{
		uint nSize = name.size();
		file.Write((char*)&nSize, sizeof(uint));
		file.Write((char*)&name[0], sizeof(char)*nSize);

		//MessageBox(NULL, ConvertToTChar(name), L"Write Entity", 0);
		//MessageBox(NULL, ConvertToTChar(name), L"Write Entity", 0);

		position.Write(file);
		rotation.Write(file);
		file.Write((char*)&material, sizeof(material));
		file.Write((char*)&baseBox, 2*sizeof(Real3));
		if(geometry)
		{
			file.WriteFlag(true);
			geometry->Write(file);
		}
		else file.WriteFlag(false);

		file.oFile << (uchar)children.size();
		forchildren
			children[i]->Write(file);
	}
	else
	{
		file.oFile << "Entity ";
		file.oFile << name;
		file.oFile << "\n{\n";
		file.oFile << "Position ";  position.Write(file);
		file.oFile << "Rotation ";  rotation.Write(file);
		if(geometry) geometry->Write(file);
		if(children.size())
		{	
			file.oFile << "Children\n";
			file.oFile << "{\n";
			forchildren
				children[i]->Write(file);
			file.oFile << "}\n";
		}
		file.oFile << "}\n";
	}
}

void Entity::WriteTree(string dir, bool binary)
{
	dir += name;
	CreateDirectory(ConvertToTChar(dir), NULL);
	dir += "\\";

	string fileName = dir + "entity.te";
	FileStream file(false);
	file.Open(fileName, binary);
	if(!file.IsOpen()) return;

	if(file.IsBinary())
	{
		uint nSize = name.size();
		file.Write((char*)&nSize, sizeof(uint));
		file.Write((char*)&name[0], sizeof(char)*nSize);

		//MessageBox(NULL, ConvertToTChar(name), L"Write Entity", 0);
		//MessageBox(NULL, ConvertToTChar(name), L"Write Entity", 0);

		position.Write(file);
		rotation.Write(file);
		file.Write((char*)&material, sizeof(material));
		file.Write((char*)&baseBox, 2*sizeof(Real3));
		if(geometry)
		{
			file.WriteFlag(true);
			geometry->Write(file);
		}
		else file.WriteFlag(false);

		file.oFile << (uchar)children.size();
		forchildren
			children[i]->WriteTree(dir, true);
	}
	else
	{
		file.oFile << "Entity ";
		file.oFile << name;
		file.oFile << "\n{\n";
		file.oFile << "Position ";  position.Write(file);
		file.oFile << "Rotation ";  rotation.Write(file);

		if(geometry) geometry->Write(file);

		if(children.size())
			forchildren
				children[i]->WriteTree(dir, false);

		file.oFile << "}\n";
	}

	file.Close();
}

//
// Reads everything from file
//

void Entity::Read(FileStream &file)
{
	if(file.IsBinary())
	{
		uint nSize;
		file.Read((char*)&nSize, sizeof(uint));
		name.resize(nSize);
		file.Read((char*)&name[0], sizeof(char)*nSize);

		//MessageBox(NULL, ConvertToTChar(name), L"Read Entity 1", 0);
		//MessageBox(NULL, ConvertToTChar(name), L"Read Entity 2", 0);

		position.Read(file);
		rotation.Read(file);
		file.Read((char*)&material, sizeof(material));
		file.Read((char*)&baseBox, 2*sizeof(Real3));

		if(file.ReadFlag())
		{
			CreateSubGeom()->Read(file);
		}
		uchar c;
		file.iFile >> c;
		for(uchar i = 0; i < c; ++i)
		{
			Entity *e = new Entity(this);
			e->Read(file);
		}
	}
	else
	{
		/*file << "Entity\n";
		file << "{\n";
		file << "Position ";  position.Write(file, false);
		file << "Rotation ";  rotation.Write(file, false);
		if(geometry) geometry->Write(file, false);
		if(children.size())
		{	
			file << "Children\n";
			file << "{\n";
			forchildren
				children[i]->Write(file, false);
			file << "}\n";
		}
		file << "}\n";*/
	}
}

//
// Set Alpha
//

void Entity::SetAlpha(float alpha) 
{
	material.Diffuse.a = 
	material.Ambient.a = 
	material.Specular.a = 
	material.Emissive.a = alpha;
}

//
// CollisionTest
// 

bool Entity::CollisionTest(const Entity *entity) const
{
	if(this != entity && entity->HasOctree() && OctreeCollisions(octree, worldMatrix, entity->octree, entity->worldMatrix))
		return true;

	for(uint i = 0; i < entity->children.size(); ++i)
		if(CollisionTest(entity->children[i])) 
			return true;

	return false;
}

/*bool CheckCollisions(Entity *entA, Entity *entB)
{
	return OctreeCollisions(entA->octree, entB->octree);
}*/